C++之类型转换
导读
提到类型转换,相信有过编程经验的小伙伴们都不陌生了。之前笔者在《NDK编程Java如何保存C或C++对象》 一文就中使用了类型强转的方式。
既然C++是继承于C的语言,那么它在类型转换上又做了哪些扩展呢?
C语言式转换
C语言式的类型转换很简单,通过一个括号即可完成强转:(Type)var;
。虽然C语言式转换简单,但是它是有不少缺点的,比如它可以在任意类型之间进行转换,比如将const类型的对象转换成非const类型的对象,
可以将一个基类的对象指针转化成一个派生类的对象指针等。这些强制转换对于C++来说显然是不太合理的,c++为了克服这些缺点,引进了4新的类型转换操作符,他们分别是:静态类型转换、动态类型转换、常类型转换和重解析类型转换。
C++的类型转换
1、静态类型转换
静态类型转换使用的是static_cast
,它可以被用于强制隐型转换,例如将non-const对象转型为const对象,将int转型为double等,
它还可以用于很多这样的转换的反向转换,例如,void* 指针转型为有类型指针,基类指针转型为派生类指针等。
所谓静态类型,就是在编译器就能确定的类型,静态类型转换的使用格式是:
static_cast<目标类型>(标识符)
例如:
int main() {
double pi = 3.1415926;
int a = (int)pi; //c语言的旧式类型转换
int b = pi; //隐式类型转换
int c = static_cast<int> (pi); //c++的新式的类型转换运算符
return 0;
}
2、动态类型转换
与静态类型转换相对的是动态类型转换,动态类型是表示在编译期间不能确定的类型,需要到运行时才能确定的类型,它使用dynamic_cast
标识,它格式是
dynamic_cast<目标类型>(标识符)
动态类型转换的一个重要作用就是将父类对象转换成派生类的对象,如果转换失败则会返回空指针。
例子:
main.cpp
class Animal{
public:
virtual void print(){
std::cout << "print print" << std::endl;
}
virtual ~Animal(){
}
};
class Cat: public Animal{
public:
void print() override{
std::cout << "print Cat" << std::endl;
}
};
class Dog:public Animal{
void print() override{
std::cout << "print Dog" << std::endl;
}
};
int main() {
Animal *animal = new Cat();
Dog *dog = dynamic_cast<Dog*>(animal); // 如果转换失败会返回空指针
if(nullptr == dog){
std::cout << "转换失败" << std::endl;
}
Cat *cat = dynamic_cast<Cat*>(animal);
if(cat){
std::cout << "转换成功" << std::endl;
}
return 0;
}
dynamic_cast的转换一定要建立在多态的基础上,也就是说父类一定要有一个虚函数或者纯虚函数才行,否则转换是编译不通过的。
public:
int a;
Fruit():a(10){
}
// 一定需要一个虚函数或者纯虚函数,否则dynamic_cast转换无法编译
virtual void show();
};
class Apple: public Fruit{
void show() override{
}
};
void printApple(Apple* apple){
}
int main() {
Fruit* apple = new Apple();
// printApple(apple); // 错误,*apple的类型是Fruit,但是函数printApple需要的是Apple指针
printApple(dynamic_cast<Apple*>(apple));
return 0;
}
3、常类型转换
常类型转换又叫脱常转换,意思就是可以将一个const变量转换成一个非const变量,使用的标识符是const_cast
。
** const_cast 最常见的用途就是将某个对象的常量性去除掉,并且只能应用于指针或引用。**
我们知道const对象只能调用const的函数,那么如果const对象也想调用非const函数怎么办?这时候就需要使用const_cast
进行脱常了。
我们来看下以下的两个例子:
void func(int & a){
std::cout << "func---a:" << a << std::endl;
a = 100; // 这里改变了引用的值,会起作用吗?需要看传递进来的变量是否是const的
}
int main() {
const int d = 30;
func(const_cast<int &>(d)); // 如果在func中修改了d的值,会起作用吗?不会
std::cout << "d:" << d << std::endl;
return 0;
}
class Fruit{
public:
int a;
Fruit():a(10){
}
};
void print(Fruit& fruit){
fruit.a = 20;
std::cout << "fruit地址:" << &fruit << std::endl;
}
int main() {
Fruit fruit;
const Fruit& constFruit = fruit;
std::cout << "a:" << fruit.a << std::endl;
std::cout << "constFruit地址:" << &constFruit << std::endl;
constFruit.a = 20; // 错误,变量a不能修改
print(&constFruit); // 错误,不能将一个const修饰的变量传递给一个非const修饰的函数
print(const_cast<Fruit&>(constFruit)); // 可以,通过const_cast类型转换,并且函数内部可以修改constFruit的变量a了
std::cout << "constFruit.a:" << constFruit.a << std::endl;
return 0;
}
以上两个例子当中,为什么一个类通过const_cast转换之后在函数内部修改该类的成员变量后会生效,而int类型经过了const_cast转换后在函数内部修改了值却不生效呢?
这里需要记住一个结论:
使用const_cast去除const限定的属性的目的不是为了修改它的内容,而是是为了函数能够接受这个实际参数。因此使用const_cast去除const限定的属性的类对象是可以改变成员变量的,但是对于内置数据类型,却表现未定义行为.
4、reinterpret_cast reinterpret_cast。是特意用于底层的强制转型,这个操作符的转换结果几乎总是与编译平台息息相关。也就是说reinterpret_casts不具跨平台移植性, 所以这里就不多做介绍了。
5、隐式转换
隐式转换给我们带来了便利的同时也会给我们带来各种各样的隐患。
让我们通过以下程序简单看看隐式转换带来的隐患:
namespace Flyer {
class A {
public:
A(int age) : age(age) {
std::cout << "自定义构造函数" << endl;
}
A(const A &a) {
std::cout << "拷贝构造" << endl;
}
~A() {
std::cout << "析构函数" << endl;
}
public:
int age;
};
}
int main() {
Flyer::A a = 10; // 隐式转换初始化,实际上是调用了A的构造函数,歧义了
a = 30; // 调用了A的构造函数
return 0;
}
在上面的程序中因为隐式转换的存在,可能是简单的赋值操作,却变成了类的构造,给人一种欺骗了我的眼睛的感觉....
如果想要去除隐式转换,彻底消除这样的隐患那该怎么办呢?答案也很简单,就是在类的构造函数上增加explicit
关键字即可:
using namespace std;
namespace Flyer {
class A {
public:
explicit A(int age) : age(age) {
std::cout << "自定义构造函数" << endl;
}
A(const A &a) {
std::cout << "拷贝构造" << endl;
}
~A() {
std::cout << "析构函数" << endl;
}
public:
int age;
};
}
int main() {
Flyer::A a = 10; // 错误,explicit不运行隐式转换
Flyer::A b{30}; // 正确,调用构造函数
b = 40; //错误,explicit不运行隐式转换
return 0;
}
如果你需要进行转换但是又不想接受隐式转换带来的隐患,那怎么办呢?在《More Effective C++》一书中作者给了我们建议:
条款 21:利用重载技术(overload)避免隐式类型转换(implicit type conversions)
推荐阅读
《C++之指针扫盲》
《C++之智能指针》
《C++之指针与引用》《C++之右值引用》
关注我,一起进步,人生不止coding!!!